package microcredit.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import microcredit.model.base.Model;
import microcredit.model.domain.Gender;
import microcredit.model.domain.LevelOfEducation;
import microcredit.model.domain.Status;
import microcredit.model.util.ModelUtils;

/**
 * Entity implementation class for Entity: Member
 *
 */
@Entity
public class Member extends Model implements Serializable {
	
	public static final String INACTIVE_REASON = "inactiveReason";
	public static final String INACTIVE_REASON_TEXT = "inactiveReasonText";
	public static final String NUMBER_CHILDREN_IN_SCHOOL = "numberChildrenInSchool";
	public static final String NUMBER_CARE_FOR = "numberCareFor";
	public static final String GENDER = "gender";
	public static final String BIRTH_DATE = "birthDate";
	public static final String FIRST_NAME = "firstName";
	public static final String LAST_NAME = "lastName";
	public static final String LEVEL_OF_EDUCATION = "levelOfEducation";

	private static final long serialVersionUID = 1L;

	public Member() {
		super();
	}
	
    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
	private Long id;
	
    @Column(name="GROUP_ID", updatable=false, insertable=false)
    private Long groupID;
    
	private MemberGroup group;
	
	private String firstName;
	@OrderBy
	private String lastName;
	
	private String middleName;
	
	@Enumerated(EnumType.STRING)
	@Column(length=1)
	private Gender gender;
	
	private String residence;
	
	@Temporal(TemporalType.DATE)
	private Date birthDate;
	
	@Enumerated(EnumType.STRING)
	@Column(length=1)
	private LevelOfEducation levelOfEducation;
	
	private Short numberCareFor;
	private Short numberChildrenInSchool;
	
	@Enumerated(EnumType.STRING)
	@Column(nullable=false, length=1)
	private Status status;
	
	@Temporal(TemporalType.TIMESTAMP)
	@Column(nullable=false)
	private Date insertStamp;
	
	@Temporal(TemporalType.TIMESTAMP)
	@Column(nullable=false)
	private Date updateStamp;
	
	@Column(length=5000)
	private String description;

	@OneToMany(mappedBy="member", fetch=FetchType.LAZY, targetEntity=MemberToGroup.class)
	@OrderBy(value="sequenceID")
	private List<MemberToGroup> memberToGroups;
	
	@OneToMany(mappedBy="member", fetch=FetchType.LAZY, targetEntity=MemberToLoan.class)
	@OrderBy(value="sequenceID")
	private List<MemberToLoan> memberToLoans;
	
	@OneToMany(mappedBy="member", fetch=FetchType.LAZY, targetEntity=MemberToTrainingType.class)
	@OrderBy(value="date")
	private List<MemberToTrainingType> memberToTrainingTypes;
	
	@OneToMany(mappedBy="member", fetch=FetchType.LAZY, targetEntity=MemberStatusHistory.class)
	@OrderBy(value="sequenceID")
	private List<MemberStatusHistory> memberStatusHistories;
	
	@PrePersist
	public void prePersist() throws ValidationException {
		if (getInsertStamp() == null) {
			setInsertStamp(ModelUtils.createTimestamp());
		}
		setUpdateStamp(getInsertStamp());
		if (getStatus() == null) {
			setStatus(Status.A);
		}
		validate();
	}
	
	@PreUpdate
	public void preUpdate() throws ValidationException {
		setUpdateStamp(ModelUtils.createTimestamp());
		validate();
	}
	
	public List<MemberStatusHistory> getMemberStatusHistories() {
		return memberStatusHistories;
	}
	
	public void setMemberStatusHistories(
			List<MemberStatusHistory> memberStatusHistories) {
		Object oldStatusHistories = this.memberStatusHistories;
		this.memberStatusHistories = memberStatusHistories;
		firePropertyChange("memberStatusHistories", oldStatusHistories, this.memberStatusHistories);
	}

	public void setMemberToLoans(List<MemberToLoan> memberToLoans) {
		Object oldmemberToLoans = this.memberToLoans;
		this.memberToLoans = memberToLoans;
		firePropertyChange("memberToLoans", oldmemberToLoans, this.memberToLoans);
	}
	
	public void setMemberToGroups(List<MemberToGroup> memberToGroups) {
		Object oldMemberToGroups = this.memberToGroups;
		this.memberToGroups = memberToGroups;
		firePropertyChange("memberToGroups", oldMemberToGroups, this.memberToGroups);
	}

	public void addMemberToLoan(MemberToLoan loan) {
		List<MemberToLoan> loans = getMemberToLoans();
		if (loans == null) {
			loans = new ArrayList<MemberToLoan>();
			memberToLoans = loans; 
		}
		loans.add(loan);
		int index = loans.indexOf(loan);
		loan.setSequenceID(index+1L);
	}
	
	public void validate() throws ValidationException {
		if (nullOrEmpty(getLastName())) {
			throw new ValidationException("Please enter a last name", LAST_NAME);
		}
		if (nullOrEmpty(getFirstName())) {
			throw new ValidationException("Please enter a first name", FIRST_NAME);
		}
		if (getGender() == null) {
			throw new ValidationException("Please select a gender", GENDER);
		}
		if (getBirthDate() == null) {
			throw new ValidationException("Please enter a valid birthdate (at least the year)", BIRTH_DATE);
		}
		if (getNumberCareFor() == null) {
			throw new ValidationException("Please enter the amount of people to care for.", NUMBER_CARE_FOR);
		}
		if (getNumberChildrenInSchool() == null) {
			throw new ValidationException("Please enter the number of children in school.", NUMBER_CHILDREN_IN_SCHOOL);
		}
		if (getNumberCareFor().compareTo(getNumberChildrenInSchool()) < 0) {
			throw new ValidationException("Please number of children in school cannot be higher than the number to care for.", NUMBER_CARE_FOR);
		}
		
		if (status != null 
				&& status.equals(Status.I)
				&& (getMemberStatusHistories() == null 
						|| getMemberStatusHistories().size() == 0)) {
			throw new ValidationException("No inactive reason defined for Member.", INACTIVE_REASON);
		}
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Member other = (Member) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

	public List<MemberToTrainingType> getMemberToTrainingTypes() {
		return memberToTrainingTypes;
	}

	public void setMemberToTrainingTypes(
			List<MemberToTrainingType> memberToTrainingTypes) {
		this.memberToTrainingTypes = memberToTrainingTypes;
	}
	
	public List<MemberToLoan> getMemberToLoans() {
		return memberToLoans;
	}

	public List<MemberToGroup> getMemberToGroups() {
		return memberToGroups;
	}

	public Long getId() {
		return id;
	}
	
	public Long getGroupID() {
		return groupID;
	}
	
	public void setGroupID(Long groupID) {
		this.groupID = groupID;
	}

	public String getFullName() {
		StringBuilder builder = new StringBuilder();
		if (getFirstName() != null && !getFirstName().isEmpty()) {
			builder.append(getFirstName());
		}
		
		if (getMiddleName() != null && !getMiddleName().isEmpty()) {
			builder.append(" ");
			builder.append(getMiddleName());
		}
		
		if (getLastName() != null && !getLastName().isEmpty()) {
			builder.append(" ");
			builder.append(getLastName());
		}
		return builder.toString();
	}
	
	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public void setId(Long id) {
		Object oldid = this.id;
		this.id = id;
		firePropertyChange("id", oldid, this.id);
	}

	public MemberGroup getGroup() {
		return group;
	}

	public void setGroup(MemberGroup group) {
		Object oldgroup = this.group;
		this.group = group;
		firePropertyChange("group", oldgroup, this.group);
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		Object oldfirstName = this.firstName;
		this.firstName = firstName;
		firePropertyChange(FIRST_NAME, oldfirstName, this.firstName);
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		Object oldlastName = this.lastName;
		this.lastName = lastName;
		firePropertyChange(LAST_NAME, oldlastName, this.lastName);
	}

	public String getMiddleName() {
		return middleName;
	}

	public void setMiddleName(String middleName) {
		Object oldmiddleName = this.middleName;
		this.middleName = middleName;
		firePropertyChange("middleName", oldmiddleName, this.middleName);
	}

	public Gender getGender() {
		return gender;
	}

	public void setGender(Gender gender) {
		Object oldgender = this.gender;
		this.gender = gender;
		firePropertyChange(GENDER, oldgender, this.gender);
	}

	public String getResidence() {
		return residence;
	}

	public void setResidence(String residence) {
		Object oldresidence = this.residence;
		this.residence = residence;
		firePropertyChange("residence", oldresidence, this.residence);
	}

	public Date getBirthDate() {
		return birthDate;
	}

	public void setBirthDate(Date birthDate) {
		Object oldbirthDate = this.birthDate;
		this.birthDate = birthDate;
		firePropertyChange(BIRTH_DATE, oldbirthDate, this.birthDate);
	}

	public LevelOfEducation getLevelOfEducation() {
		return levelOfEducation;
	}

	public void setLevelOfEducation(LevelOfEducation levelOfEducation) {
		Object oldlevelOfEducation = this.levelOfEducation;
		this.levelOfEducation = levelOfEducation;
		firePropertyChange("levelOfEducation", oldlevelOfEducation,
				this.levelOfEducation);
	}

	public Short getNumberCareFor() {
		return numberCareFor;
	}

	public void setNumberCareFor(Short numberCareFor) {
		Object oldnumberCareFor = this.numberCareFor;
		this.numberCareFor = numberCareFor;
		firePropertyChange(NUMBER_CARE_FOR, oldnumberCareFor, this.numberCareFor);
	}

	public Short getNumberChildrenInSchool() {
		return numberChildrenInSchool;
	}

	public void setNumberChildrenInSchool(Short numberChildrenInSchool) {
		Object oldnumberChildrenInSchool = this.numberChildrenInSchool;
		this.numberChildrenInSchool = numberChildrenInSchool;
		firePropertyChange(NUMBER_CHILDREN_IN_SCHOOL, oldnumberChildrenInSchool,
				this.numberChildrenInSchool);
	}

	public Status getStatus() {
		return status;
	}

	public void setStatus(Status status) {
		Object oldstatus = this.status;
		this.status = status;
		firePropertyChange("status", oldstatus, this.status);
	}

	public Date getInsertStamp() {
		return insertStamp;
	}

	public void setInsertStamp(Date insertStamp) {
		Object oldinsertStamp = this.insertStamp;
		this.insertStamp = insertStamp;
		firePropertyChange("insertStamp", oldinsertStamp, this.insertStamp);
	}

	public Date getUpdateStamp() {
		return updateStamp;
	}

	public void setUpdateStamp(Date updateStamp) {
		Object oldupdateStamp = this.updateStamp;
		this.updateStamp = updateStamp;
		firePropertyChange("updateStamp", oldupdateStamp, this.updateStamp);
	}
	
	public static String formatID(Long memberID) {
		return ModelUtils.formatID(memberID, 4);
	}

	
	public MemberStatusHistory getLastStatusHistory() {
		if (getMemberStatusHistories() != null
				&& getMemberStatusHistories().size() > 0) {
			return getMemberStatusHistories().get(getMemberStatusHistories().size()-1);
		}
		return null;
	}
	
	@Override
	public String toString() {
		if (getId() == null) {
			return "Member";			
		} else {
			return "Member ["+formatID(getId())+", "+getLastName()+"]";
		}
	}
	
}
